﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Reflection;

namespace Ruandao.MiniAccess.OrMap
{
    /// <summary>
    /// 映射文件类，用来读取映射文件的内容。
    /// </summary>
    public class OrMapFile
    {
        private string entityNamespace;
        private bool isDebug;
        private IList<EntityTag> entityTagList = new List<EntityTag>();

        /// <summary>
        /// 构造函数。构造OrMapFile所完成的功能是定位映射文件并且读出所有配置项的内容。
        /// 映射文件的文件名约定由实体所在程序集的名称来确定，格式是程序集名称（不包括.dll）
        /// 加上".mdm.xml"的扩展名。
        /// 映射文件的位置在应用程序所在目录或者程序集所在目录下，也可以在上述两个目录的
        /// minidacmap子目录下。
        /// <br/>异常：IOException(无法定位映射文件), OrMapException(mdm.xml格式错误)
        /// </summary>
        /// <param name="assembly">需要映射的实体所在的程序集，用来确定映射文件。</param>
        /// <param name="providerName">映射文件中provider的name属性值。</param>
        /// <param name="accessType"></param>
        public OrMapFile(Assembly assembly, string accessType, string providerName)
        {
            string ormFile = LocateMdmFile(assembly);

            XmlDocument dom = new XmlDocument();
            dom.Load(ormFile);

            if (dom.DocumentElement.LocalName != "miniaccess-mapping")
            {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：文件的根元素必须是<miniaccess-mapping>", 
                        assembly.GetName().Name, ormFile)
                    );
            }

            // 定位到当前设定的provider所在的元素。
            XmlNode typeNode = null;
            foreach (XmlNode node in dom.DocumentElement.ChildNodes)
            {
                if (node.LocalName != "access-type") continue;

                if (node.Attributes["name"].Value == accessType)
                {
                    typeNode = node;
                    break;
                }

            }

            if (typeNode == null)
            {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：根元素下找不到指定access-type名称的配置元素<access-type name=\"{2}\">",
                        assembly.GetName().Name, ormFile, accessType)
                );
            }

            // 定位到当前设定的provider所在的元素。
            XmlNode providerNode = null;
            foreach (XmlNode node in typeNode.ChildNodes)
            {
                if (node.LocalName != "provider") continue;

                if (node.Attributes["name"].Value == providerName)
                {
                    entityNamespace = node.Attributes["entity-namespace"].Value;
                    if (string.IsNullOrEmpty(entityNamespace))
                        throw new OrMapException(
                                string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：<provider name=\"{2}\">元素的entity-namespace不能是null或者empty", 
                                assembly.GetName().Name, ormFile, providerName)
                        );
                    
                    isDebug = true;
                    if( node.Attributes["is-debug"] != null)
                    {
                        if (node.Attributes["is-debug"].Value.ToLower() == "false") isDebug = false;
                    }
                    
                    providerNode = node;
                    break;
                }

            }

            if (providerNode == null)
            {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：找不到指定provider名称的配置元素<provider name=\"{2}\">", 
                        assembly.GetName().Name, ormFile, providerName)
                );
            }

            ReadOrMapSettings(providerNode);
        }

        /// <summary>
        /// 如果IsDebug为真，则每次运行时重新生成数据库中的存储过程。
        /// </summary>
        public bool IsDebug
        {
            get { return isDebug; }
        }

        /// <summary>
        /// 实体类型所在的命名空间。
        /// </summary>
        public string EntityNamespace
        {
            get { return entityNamespace; }
        }

        /// <summary>
        /// 获取映射文件中所有的实体列表。
        /// </summary>
        public IList<EntityTag> EntityTagList
        {
            get { return entityTagList; }
        }

        /// <summary>
        /// 定位Or映射文件的位置。
        /// <br/>异常：OrMapException(没有找到文件)
        /// </summary>
        /// <param name="assembly">实体所在的程序集。</param>
        /// <returns>映射文件的绝对路径。</returns>
        private string LocateMdmFile(Assembly assembly)
        {
            string mdmfile = assembly.GetName().Name + ".mdm.xml";

            string path;
            string pathfile = "";

            path = Path.GetDirectoryName(assembly.Location);
            pathfile = Path.Combine(path, mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            path = AppDomain.CurrentDomain.BaseDirectory;

            pathfile = Path.Combine(path, mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "Bin\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "App_Data\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "minidacmap\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            throw new OrMapException("无法定位" + mdmfile + "文件，其位置约定是应用程序域的主目录或者主目录下的名为Bin、App_Data或minidacmap的子目录，或者是和实体程序集位于同一个目录下。");
        }

        private void ReadOrMapSettings(XmlNode providerNode)
        {

            entityTagList = new List<EntityTag>();
            // 遍历所有的实体类型
            foreach (XmlNode node in providerNode.ChildNodes)
            {
                //检测node是class, 如果不是则抛出错误。
                if (node.LocalName != "entity")
                {
                    throw new OrMapException("映射文件格式不正确：文件的<provider>元素下必须是<entity>元素。");
                }
                EntityTag entity = ReadTableMapFromXml(node);

                entity.PropertyList = new List<PropertyTag>();

                // 遍历所有的属性、主键和外建字段。
                foreach (XmlNode subnode in node.ChildNodes)
                {
                    PropertyTag property = ReadColumnMapFromXml(subnode);
                    entity.PropertyList.Add(property);
                }
                entityTagList.Add(entity);
            }
        }

        // 注意node.Attributes["..."]为null表示xml文件中没有指定该属性，因而程序
        // 程序可以把它解释为使用系统的默认值，而node.Attributes["..."]为""则表示
        // xml文件中显示指定该属性为空字符串。
        private EntityTag ReadTableMapFromXml(XmlNode node)
        {
            EntityTag entity = new EntityTag(node.Attributes["name"].Value);
            if (node.Attributes["table"] != null)
            {
                entity.Table = node.Attributes["table"].Value;
            }
            if (node.Attributes["command-type"] != null)
            {
                entity.CommandType = node.Attributes["command-type"].Value;
            }
            if (node.Attributes["insert"] != null)
            {
                entity.Insert = node.Attributes["insert"].Value;
            }
            if (node.Attributes["delete"] != null)
            {
                entity.Delete = node.Attributes["delete"].Value;
            }
            if (node.Attributes["multiple-delete"] != null)
            {
                entity.MultipleDelete = node.Attributes["multiple-delete"].Value;
            }
            if (node.Attributes["update"] != null)
            {
                entity.Update = node.Attributes["update"].Value;
            }
            if (node.Attributes["fetch"] != null)
            {
                entity.Fetch = node.Attributes["fetch"].Value;
            }
            if (node.Attributes["search"] != null)
            {
                entity.Search = node.Attributes["search"].Value;
            }
            return entity;
        }

        // 注意node.Attributes["..."]为null表示xml文件中没有指定该属性，因而程序
        // 程序可以把它解释为使用系统的默认值，而node.Attributes["..."]为""则表示
        // xml文件中显示指定该属性为空字符串。
        private PropertyTag ReadColumnMapFromXml(XmlNode node)
        {
            ColumnMapType type = ColumnMapTypeFromString(node.LocalName);

            PropertyTag property = new PropertyTag(type, node.Attributes["name"].Value );

            if (node.Attributes["column"] != null)
            {
                property.Column = node.Attributes["column"].Value;
            }
            if (node.Attributes["column-type"] != null)
            {
                property.ColumnType = node.Attributes["column-type"].Value;
            }
            if (node.Attributes["update"] != null)
            {
                property.Update = node.Attributes["update"].Value;
            }
            if (node.Attributes["insert"] != null)
            {
                property.Insert = node.Attributes["insert"].Value;
            }
            return property;
        }

        private ColumnMapType ColumnMapTypeFromString(string name)
        {
            name = name.ToLower();
            switch (name)
            {
                case "primary-key":
                    return ColumnMapType.PrimaryKeyMap;
                case "one-to-one":
                    return ColumnMapType.OneToOneMap;
                case "many-to-one":
                    return ColumnMapType.ManyToOneMap;
                case "property":
                    return ColumnMapType.PropertyMap;
                default:
                    throw new OrMapException("不支持的字段映射类型：" + name);
            }
        }
    }
}
